 /*******************************************************************************
  * Copyright (c) 2006, 2007 IBM Corporation and others. All rights reserved.
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
  * and is available at http://www.eclipse.org/legal/epl-v10.html
  *
  * Contributors: IBM Corporation - initial API and implementation
  ******************************************************************************/
 package org.eclipse.osgi.internal.verifier;

 import java.io.ByteArrayInputStream ;
 import java.io.IOException ;
 import java.math.BigInteger ;
 import java.security.*;
 import java.security.cert.*;
 import java.security.cert.Certificate ;
 import java.text.DateFormat ;
 import java.text.ParseException ;
 import java.text.SimpleDateFormat ;
 import java.util.*;

 import javax.security.auth.x500.X500Principal ;
 import org.eclipse.osgi.framework.log.FrameworkLogEntry;
 import org.eclipse.osgi.internal.provisional.verifier.CertificateChain;
 import org.eclipse.osgi.internal.provisional.verifier.CertificateTrustAuthority;
 import org.eclipse.osgi.util.NLS;

 /**
  * This class processes a PKCS7 file. See RFC 2315 for specifics.
  */
 public class PKCS7Processor implements CertificateChain, JarVerifierConstant {

     private static CertificateFactory certFact;

     static {
         try {
             certFact = CertificateFactory.getInstance("X.509"); //$NON-NLS-1$
 } catch (CertificateException e) {
             SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
         }
     }

     private String certChain;
     private Certificate [] certificates;
     private Certificate [] tsaCertificates;
     private boolean trusted;

     // key(object id) = value(structure)
 private Map signedAttrs;

     // key(object id) = value(structure)
 private Map unsignedAttrs;

     // store the signature of a signerinfo
 private byte signature[];
     private String digestAlgorithm;
     private String signatureAlgorithm;

     private Certificate signerCert;
     private Date signingTime;

     String oid2String(int oid[]) {
         StringBuffer sb = new StringBuffer ();
         for (int i = 0; i < oid.length; i++) {
             if (i > 0)
                 sb.append('.');
             sb.append(oid[i]);
         }
         return sb.toString();
     }

     String findEncryption(int encOid[]) throws NoSuchAlgorithmException {
         if (Arrays.equals(DSA_OID, encOid)) {
             return "DSA"; //$NON-NLS-1$
 }
         if (Arrays.equals(RSA_OID, encOid)) {
             return "RSA"; //$NON-NLS-1$
 }
         throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(encOid)); //$NON-NLS-1$
 }

     String findDigest(int digestOid[]) throws NoSuchAlgorithmException {
         if (Arrays.equals(SHA1_OID, digestOid)) {
             return SHA1_STR;
         }
         if (Arrays.equals(MD5_OID, digestOid)) {
             return MD5_STR;
         }
         if (Arrays.equals(MD2_OID, digestOid)) {
             return MD2_STR;
         }
         throw new NoSuchAlgorithmException("No algorithm found for " + oid2String(digestOid)); //$NON-NLS-1$
 }

     /*
      * static void printBP(BERProcessor bp, int depth) {
      * System.out.print(depth); for(int i = 0; i < depth; i++)
      * System.out.print(" "); System.out.println(bp); }
      *
      * static void dumpSeq(BERProcessor bp, int depth) {
      * while(!bp.endOfSequence()) { printBP(bp, depth); if (bp.constructed) {
      * dumpSeq(bp.stepInto(), depth+1); } bp.stepOver(); } }
      *
      * void hexDump(byte buffer[], int off, int len) { for(int i = 0; i < len;
      * i++) { System.out.print(Integer.toString(buffer[i]&0xff, 16) + " "); if
      * (i % 16 == 15) System.out.println(); } System.out.println(); }
      */

     public PKCS7Processor(String certChain, boolean trusted, byte[][] certificates, long signingTime) throws CertificateException {
         this.certChain = certChain;
         this.trusted = trusted;
         this.certificates = new Certificate [certificates.length];
         for (int i = 0; i < certificates.length; i++)
             this.certificates[i] = certFact.generateCertificate(new ByteArrayInputStream (certificates[i]));
         if (signingTime > Long.MIN_VALUE)
             this.signingTime = new Date(signingTime);
     }

     public PKCS7Processor(byte pkcs7[], int pkcs7Offset, int pkcs7Length) throws IOException , CertificateException, NoSuchAlgorithmException {

         // First grab the certificates
 List certs = null;

         BERProcessor bp = new BERProcessor(pkcs7, pkcs7Offset, pkcs7Length);

         // Just do a sanity check and make sure we are actually doing a PKCS7
 // stream
 // PKCS7: Step into the ContentType
 bp = bp.stepInto();
         if (!Arrays.equals(bp.getObjId(), SIGNEDDATA_OID)) {
             throw new IOException ("Not a valid PKCS#7 file"); //$NON-NLS-1$
 }

         // PKCS7: Process the SignedData structure
 bp.stepOver(); // (**wrong comments**) skip over the oid
 bp = bp.stepInto(); // go into the Signed data
 bp = bp.stepInto(); // It is a structure;
 bp.stepOver(); // Yeah, yeah version = 1
 bp.stepOver(); // We'll see the digest stuff again; digestAlgorithms

         // process the encapContentInfo structure
 processEncapContentInfo(bp);

         bp.stepOver();

         // PKCS7: check if the class tag is 0
 if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 0) {
             // process the certificate elements inside the signeddata strcuture
 certs = processCertificates(bp);
         }

         if (certs == null || certs.size() < 1)
             throw new SecurityException ("There are no certificates in the .RSA/.DSA file!"); //$NON-NLS-1$

         // Okay, here are our certificates.
 bp.stepOver();
         if (bp.classOfTag == BERProcessor.UNIVERSAL_TAGCLASS && bp.tag == 1) {
             bp.stepOver(); // Don't use the CRLs if present
 }

         processSignerInfos(bp, certs);

         // construct the cert path
 certs = constructCertPath(certs, signerCert);

         // set the cert chain variable
 int numCerts = certs.size();
         StringBuffer sb = new StringBuffer ();
         for (int i = 0; i < numCerts; i++) {
             X509Certificate x509Cert = ((X509Certificate) certs.get(i));
             sb.append(x509Cert.getSubjectDN().getName());
             sb.append("; "); //$NON-NLS-1$
 }
         certChain = sb.toString();

         // initialize the certificates
 certificates = (Certificate []) certs.toArray(new Certificate [numCerts]);

         // if this pkcs7process is tsa asn.1 block, the signingTime should already be set
 if (null == signingTime)
             signingTime = PKCS7DateParser.parseDate(this);
     }

     private void processEncapContentInfo(BERProcessor bp) throws IOException {
         // check immediately if TSTInfo is there
 BERProcessor encapContentBERS = bp.stepInto();
         if (Arrays.equals(encapContentBERS.getObjId(), TIMESTAMP_TST_OID)) {

             // eContent
 encapContentBERS.stepOver();
             BERProcessor encapContentBERS1 = encapContentBERS.stepInto();

             // obtain eContent octet structure
 byte bytesman[] = encapContentBERS1.getBytes();
             BERProcessor eContentStructure = new BERProcessor(bytesman, 0, bytesman.length);

             // pointing at 'version Integer' now
 BERProcessor eContentBER = eContentStructure.stepInto();
             int tsaVersion = eContentBER.getIntValue().intValue();

             if (tsaVersion != 1) {
                 throw new IOException ("Not a version 1 time-stamp token"); //$NON-NLS-1$
 }

             // policty : TSAPolicyId
 eContentBER.stepOver();

             // messageImprint : MessageImprint
 eContentBER.stepOver();

             // serialNumber : INTEGER
 eContentBER.stepOver();

             // genTime : GeneralizedTime
 eContentBER.stepOver();

             // check time ends w/ 'Z'
 String dateString = new String (eContentBER.getBytes());
             if (!dateString.endsWith("Z")) { //$NON-NLS-1$
 throw new IOException ("Wrong dateformat used in time-stamp token"); //$NON-NLS-1$
 }

             // create the appropriate date time string format
 // date format could be yyyyMMddHHmmss[.s...]Z or yyyyMMddHHmmssZ
 int dotIndex = dateString.indexOf('.');
             StringBuffer dateFormatSB = new StringBuffer ("yyyyMMddHHmmss"); //$NON-NLS-1$
 if (dotIndex != -1) {
                 // yyyyMMddHHmmss[.s...]Z, find out number of s in the bracket
 int noS = dateString.indexOf('Z') - 1 - dotIndex;
                 dateFormatSB.append('.');

                 // append s
 for (int i = 0; i < noS; i++) {
                     dateFormatSB.append('s');
                 }
             }
             dateFormatSB.append("'Z'"); //$NON-NLS-1$

             try {
                 DateFormat dateFormt = new SimpleDateFormat (dateFormatSB.toString());
                 dateFormt.setTimeZone(TimeZone.getTimeZone("GMT")); //$NON-NLS-1$
 signingTime = dateFormt.parse(dateString);
             } catch (ParseException e) {
                 throw new IOException (JarVerifierMessages.PKCS7_Parse_Signing_Time_1);
             }
         }
     }

     private List constructCertPath(List certs, Certificate targetCert) {
         List certsList = new ArrayList();
         certsList.add(targetCert);

         X509Certificate currentCert = (X509Certificate) targetCert;
         int numIteration = certs.size();
         int i = 0;
         while (i < numIteration) {

             X500Principal subject = currentCert.getSubjectX500Principal();
             X500Principal issuer = currentCert.getIssuerX500Principal();

             if (subject.equals(issuer)) {
                 // the cert path has been constructed
 break;
             }

             currentCert = null;
             Iterator itr = certs.iterator();

             while (itr.hasNext()) {
                 X509Certificate tempCert = (X509Certificate) itr.next();

                 if (tempCert.getSubjectX500Principal().equals(issuer)) {
                     certsList.add(tempCert);
                     currentCert = tempCert;
                 }
             }

             i++;
         }

         return certsList;
     }

     public void validateCerts() throws CertificateExpiredException, CertificateNotYetValidException, InvalidKeyException, SignatureException {
         if (certificates == null || certificates.length == 0) {
             throw new SecurityException ("There are no certificates in the signature block file!"); //$NON-NLS-1$
 }

         int len = certificates.length;

         // check the certs validity and signatures
 for (int i = 0; i < len; i++) {
             X509Certificate currentX509Cert = (X509Certificate) certificates[i];

             if (signingTime == null)
                 currentX509Cert.checkValidity();
             else
                 currentX509Cert.checkValidity(signingTime);

             try {
                 if (i == len - 1) {
                     if (currentX509Cert.getSubjectDN().equals(currentX509Cert.getIssuerDN()))
                         currentX509Cert.verify(currentX509Cert.getPublicKey());
                 } else {
                     X509Certificate nextX509Cert = (X509Certificate) certificates[i + 1];
                     currentX509Cert.verify(nextX509Cert.getPublicKey());
                 }
             } catch (NoSuchAlgorithmException e) {
                 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
                 throw new SecurityException (NLS.bind(JarVerifierMessages.No_Such_Algorithm_Excep, new String [] {e.getMessage()}));
             } catch (NoSuchProviderException e) {
                 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
                 throw new SecurityException (NLS.bind(JarVerifierMessages.No_Such_Provider_Excep, new String [] {e.getMessage()}));
             } catch (CertificateException e) {
                 SignedBundleHook.log(e.getMessage(), FrameworkLogEntry.ERROR, e);
                 throw new SecurityException (NLS.bind(JarVerifierMessages.Validate_Certs_Certificate_Exception, new String [] {e.getMessage()}));
             }
         }
     }

     private Certificate processSignerInfos(BERProcessor bp, List certs) throws CertificateException, NoSuchAlgorithmException {
         // We assume there is only one SingerInfo element

         // PKCS7: SignerINFOS processing
 bp = bp.stepInto(); // Step into the set of signerinfos
 bp = bp.stepInto(); // Step into the signerinfo sequence

         // make sure the version is 1
 BigInteger signerInfoVersion = bp.getIntValue();
         if (signerInfoVersion.intValue() != 1) {
             throw new CertificateException(JarVerifierMessages.PKCS7_SignerInfo_Version_Not_Supported);
         }

         // PKCS7: version CMSVersion
 bp.stepOver(); // Skip the version

         // PKCS7: sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
 BERProcessor issuerAndSN = bp.stepInto();
         X500Principal signerIssuer = new X500Principal (new ByteArrayInputStream (issuerAndSN.buffer, issuerAndSN.offset, issuerAndSN.endOffset - issuerAndSN.offset));
         issuerAndSN.stepOver();
         BigInteger sn = issuerAndSN.getIntValue();

         // initilize the newSignerCert to the issuer cert of leaf cert
 Certificate newSignerCert = null;

         Iterator itr = certs.iterator();
         // PKCS7: compuare the issuers in the issuerAndSN BER equals to the issuers in Certs generated at the beginning of this method
 // it seems like there is no neeed, cause both ways use the same set of bytes
 while (itr.hasNext()) {
             X509Certificate cert = (X509Certificate) itr.next();
             if (cert.getIssuerX500Principal().equals(signerIssuer) && cert.getSerialNumber().equals(sn)) {
                 newSignerCert = cert;
                 break;
             }
         }

         if (newSignerCert == null)
             throw new CertificateException("Signer certificate not in pkcs7block"); //$NON-NLS-1$

         // set the signer cert
 signerCert = newSignerCert;

         // PKCS7: skip over the sid [SignerIdentifier : issuerAndSerialNumber or subjectKeyIdentifer]
 bp.stepOver(); // skip the issuer name and serial number

         // PKCS7: digestAlgorithm DigestAlgorithmIdentifier
 BERProcessor digestAlg = bp.stepInto();
         digestAlgorithm = findDigest(digestAlg.getObjId());

         // PKCS7: check if the next one if context class for signedAttrs
 bp.stepOver(); // skip the digest alg

         // process the signed attributes if there is any
 processSignedAttributes(bp);

         // PKCS7: signatureAlgorithm for this SignerInfo
 BERProcessor encryptionAlg = bp.stepInto();
         signatureAlgorithm = findEncryption(encryptionAlg.getObjId());
         bp.stepOver(); // skip the encryption alg

         // PKCS7: signature
 signature = bp.getBytes();

         // PKCS7: Step into the unsignedAttrs,
 bp.stepOver();

         // process the unsigned attributes if there is any
 processUnsignedAttributes(bp);

         return newSignerCert;
     }

     private void processUnsignedAttributes(BERProcessor bp) {

         if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS && bp.tag == 1) {

             // there are some unsignedAttrs are found!!
 unsignedAttrs = new HashMap();

             // step into a set of unsigned attributes, I believe, when steps
 // into here, the 'poiter' is pointing to the first element
 BERProcessor unsignedAttrsBERS = bp.stepInto();
             do {
                 // process the unsignedAttrsBER by getting the attr type first,
 // then the strcuture for the type
 BERProcessor unsignedAttrBER = unsignedAttrsBERS.stepInto();

                 // check if it is timestamp attribute type
 int objID[] = unsignedAttrBER.getObjId();
                 // if(Arrays.equals(TIMESTAMP_OID, objID)) {
 // System.out.println("This is a timestamp type, to continue");
 // }

                 // get the structure for the attribute type
 unsignedAttrBER.stepOver();
                 byte structure[] = unsignedAttrBER.getBytes();
                 unsignedAttrs.put(objID, structure);
                 unsignedAttrsBERS.stepOver();
             } while (!unsignedAttrsBERS.endOfSequence());
         }
     }

     private void processSignedAttributes(BERProcessor bp) {
         if (bp.classOfTag == BERProcessor.CONTEXTSPECIFIC_TAGCLASS) {

             // process the signed attributes
 signedAttrs = new HashMap();

             BERProcessor signedAttrsBERS = bp.stepInto();
             do {
                 BERProcessor signedAttrBER = signedAttrsBERS.stepInto();
                 int[] signedAttrObjID = signedAttrBER.getObjId();

                 // step over to the attribute value
 signedAttrBER.stepOver();

                 byte[] signedAttrStructure = signedAttrBER.getBytes();

                 signedAttrs.put(signedAttrObjID, signedAttrStructure);

                 signedAttrsBERS.stepOver();
             } while (!signedAttrsBERS.endOfSequence());
             bp.stepOver();
         }
     }

     /**
      * Returns the Certificate of the signer of this PKCS7Block
      */
     public Certificate getSigner() {
         if (certificates == null || certificates.length == 0)
             return null;
         return certificates[0];
     }

     public Certificate getRoot() {
         if (certificates == null || certificates.length == 0)
             return null;
         return certificates[certificates.length - 1];
     }

     public Certificate [] getCertificates() {
         return certificates;
     }

     /**
      * Returns the list of X500 distinguished names that make up the signature chain. Each
      * distinguished name is separated by a ';'.
      */
     public String getChain() {
         return certChain;
     }

     /**
      * Returns true if the signer certificate is trusted
      * @return true if the signer certificate is trusted
      */
     public boolean isTrusted() {
         return trusted;
     }

     public boolean equals(Object obj) {
         if (!(obj instanceof CertificateChain))
             return false;
         if (certificates == null)
             return false;
         CertificateChain chain = (CertificateChain) obj;
         if((signingTime == null ? chain.getSigningTime() != null : !signingTime.equals(chain.getSigningTime())))
             return false;
         if (trusted != chain.isTrusted() || (certChain == null ? chain.getChain() != null : !certChain.equals(chain.getChain())))
             return false;
         Certificate [] otherCerts = chain.getCertificates();
         if (otherCerts == null || certificates.length != otherCerts.length)
             return false;
         for (int i = 0; i < certificates.length; i++)
             if (!certificates[i].equals(otherCerts[i]))
                 return false;
         return true;
     }

     public void verifySFSignature(byte data[], int dataOffset, int dataLength) throws InvalidKeyException, NoSuchAlgorithmException, SignatureException {
         Signature sig = Signature.getInstance(digestAlgorithm + "with" + signatureAlgorithm); //$NON-NLS-1$
 sig.initVerify(signerCert.getPublicKey());
         sig.update(data, dataOffset, dataLength);
         if (!sig.verify(signature)) {
             throw new SignatureException(JarVerifierMessages.Signature_Not_Verify);
         }
     }

     /**
      * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
      *
      * @return map if there is any signed attributes, null otherwise
      */
     public Map getUnsignedAttrs() {
         return unsignedAttrs;
     }

     /**
      * Return a map of signed attributes, the key(objid) = value(PKCSBlock in bytes for the key)
      *
      * @return map if there is any signed attributes, null otherwise
      */
     public Map getSignedAttrs() {
         return signedAttrs;
     }

     /**
      *
      * @param bp
      * @return a List of certificates from target cert to root cert in order
      *
      * @throws CertificateException
      */
     private List processCertificates(BERProcessor bp) throws CertificateException {
         List rtvList = new ArrayList(3);

         // Step into the first certificate-element
 BERProcessor certsBERS = bp.stepInto();

         do {
             X509Certificate x509Cert = (X509Certificate) certFact.generateCertificate(new ByteArrayInputStream (certsBERS.buffer, certsBERS.offset, certsBERS.endOffset - certsBERS.offset));

             if (x509Cert != null) {
                 rtvList.add(x509Cert);
             }

             // go to the next cert element
 certsBERS.stepOver();
         } while (!certsBERS.endOfSequence());

         // Collections.reverse(rtvList);
 return rtvList;
     }

     void determineTrust(CertificateTrustAuthority certsTrust) {
         try {
             certsTrust.checkTrust(certificates);
             if (null != tsaCertificates) {
                 certsTrust.checkTrust(tsaCertificates);
             }
             trusted = true;
         } catch (CertificateException e) {
             trusted = false;
         }
     }

     public Date getSigningTime() {
         return signingTime;
     }

     void setTSACertificates(Certificate [] tsaCertificates) {
         this.tsaCertificates = tsaCertificates;
     }

     /*
      public static void main(String[] args) throws InvalidKeyException, CertificateException, NoSuchAlgorithmException, SignatureException, KeyStoreException, IOException {
      byte buffer[] = new byte[65536];
      int len = System.in.read(buffer);
      byte manifestBuff[] = new byte[65536];
      int rc = new FileInputStream("man").read(manifestBuff);
      PKCS7Processor p7 = new PKCS7Processor(buffer, 0, len, manifestBuff, 0, rc);
      System.out.println(p7.getSignerCertificate());
      System.out.println(p7.getCertificateChain());
      }
      */
 }
